﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DefaultValueConverter.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   The default value converter.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using System;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// The default value converter.
    /// </summary>
    public class DefaultValueConverter
    {
        #region Fields

        /// <summary>
        ///   The try parse method.
        /// </summary>
        private static readonly MethodInfo TryParseMethod = typeof(Enum).GetMethods().FirstOrDefault(x => x.GetParameters().Length == 2);

        /// <summary>
        ///   The instance.
        /// </summary>
        public static readonly DefaultValueConverter Instance = new DefaultValueConverter();

        /// <summary>
        ///   The _default converters.
        /// </summary>
        private readonly IConverter[] _defaultConverters = CreateDefaultConverters();

        #endregion
        
        public object ChangeType(object value, Type targetType)
        {
            object result;
            if (!TryChangeType(value, targetType, out result))
            {
                throw new InvalidOperationException("Cannot change type.");
            }
            return result;
        }

        public bool TryChangeType(object value, Type targetType, out object result)
        {
            if (value == null)
            {
                result = null;
                return IsNullableType(targetType);
            }

            var valueType = value.GetType();
            if (targetType.IsAssignableFrom(valueType))
            {
                result = value;
                return true;
            }

            if (targetType.IsEnum)
            {
                return TryConvertEnum(value, targetType, out result);
            }

            var type = Nullable.GetUnderlyingType(targetType) ?? targetType;
            
            var converter = GetDefaultConverter(type);
            if (converter != null)
            {
                if (converter.TryConvert(value, out result))
                {
                    if (type != targetType)
                    {
                        result = Activator.CreateInstance(targetType, result);
                    }

                    return true;
                }
            }

            result = null;
            return false;
        }

        private bool TryConvertEnum(object value, Type targetType, out object result)
        {
            var converter = GetDefaultConverter(targetType);
            if (converter != null)
            {
                if (value is string)
                {
                    var args = new[] { value, null };
                    if ((bool)TryParseMethod.MakeGenericMethod(targetType).Invoke(null, args))
                    {
                        result = args[1];
                        return true;
                    }
                }
                else if (converter.TryConvert(value, out result))
                {
                    if (Enum.IsDefined(targetType, value))
                    {
                        result = Enum.ToObject(targetType, result);
                        return true;
                    }
                }
            }

            result = null;
            return false;
        }

        private static IConverter[] CreateDefaultConverters()
        {
            var result = new IConverter[(int)TypeCode.String + 1];
            result[(int)TypeCode.DBNull] = null;
            result[(int)TypeCode.Boolean] = new BooleanConverter();
            result[(int)TypeCode.Char] = new CharConverter();
            result[(int)TypeCode.SByte] = new SByteConverter();
            result[(int)TypeCode.Byte] = new ByteConverter();
            result[(int)TypeCode.Int16] = new Int16Converter();
            result[(int)TypeCode.UInt16] = new UInt16Converter();
            result[(int)TypeCode.Int32] = new Int32Converter();
            result[(int)TypeCode.UInt32] = new UInt32Converter();
            result[(int)TypeCode.Int64] = new Int64Converter();
            result[(int)TypeCode.UInt64] = new UInt64Converter();
            result[(int)TypeCode.Single] = new SingleConverter();
            result[(int)TypeCode.Double] = new DoubleConverter();
            result[(int)TypeCode.Decimal] = new DecimalConverter();
            result[(int)TypeCode.DateTime] = new DateTimeConverter();
            result[(int)TypeCode.String] = new StringConverter();
            return result;
        }

        protected virtual IConverter GetDefaultConverter(Type targetType)
        {
            var index = (int)Type.GetTypeCode(targetType);
            if (index < 0 || index >= _defaultConverters.Length)
            {
                return null;
            }

            return _defaultConverters[index];
        }

        private static bool IsNullableType(Type type)
        {
            return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
        }

        #region Nested type: BooleanConverter

        public class BooleanConverter : Converter<bool>
        {
            public override bool TryConvert(string value, out bool result)
            {
                return bool.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out bool result)
            {
                result = value != 0L;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out bool result)
            {
                result = value != 0ul;
                return true;
            }

            public override bool TryConvert(decimal value, out bool result)
            {
                result = value != 0M;
                return true;
            }

            public override bool TryConvert(double value, out bool result)
            {
                result = value != 0.0;
                return true;
            }

            public override bool TryConvert(float value, out bool result)
            {
                result = value != 0.0f;
                return true;
            }
        }

        #endregion

        #region Nested type: ByteConverter

        public class ByteConverter : OrdinalConverter<byte>
        {
            public override bool TryConvert(string value, out byte result)
            {
                return Byte.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out byte result)
            {
                if (value < Byte.MinValue || value > Byte.MaxValue)
                {
                    result = default(byte);
                    return false;
                }

                result = (byte)value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out byte result)
            {
                if (value < Byte.MinValue || value > Byte.MaxValue)
                {
                    result = default(byte);
                    return false;
                }

                result = (byte)value;
                return true;
            }
        }

        #endregion

        #region Nested type: CharConverter

        /// <summary>
        /// The char converter.
        /// </summary>
        public class CharConverter : OrdinalConverter<char>
        {
            public override bool TryConvert(string value, out char result)
            {
                if (value == null || value.Length != 1)
                {
                    result = default(char);
                    return false;
                }

                result = value[0];
                return true;
            }

            public override bool TryConvert(long value, out char result)
            {
                if (value > char.MaxValue || value < char.MinValue)
                {
                    result = default(char);
                    return false;
                }

                result = (char)value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out char result)
            {
                if (value > char.MaxValue)
                {
                    result = default(char);
                    return false;
                }

                result = (char)value;
                return true;
            }
        }

        #endregion

        #region Nested type: Converter

        /// <summary>
        /// Base converter implementation.
        /// </summary>
        /// <typeparam name="T">The target type of the converter.</typeparam>
        public abstract class Converter<T> : IConverter
        {
            protected Converter()
            {
                IsNullable = !typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) != null;
            }

            /// <summary>
            /// Gets a value indicating whether IsNullable.
            /// </summary>
            protected bool IsNullable { get; private set; }

            #region IConverter Members

            bool IConverter.TryConvert(object value, out object result)
            {
                T typedResult;
                if (TryConvert(value, out typedResult))
                {
                    result = typedResult;
                    return true;
                }

                result = null;
                return false;
            }

            Type IConverter.TargetType
            {
                get { return typeof(T); }
            }

            #endregion

            public virtual bool TryConvert(DateTime value, out T result)
            {
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(decimal value, out T result)
            {
                result = default(T);
                return true;
            }

            public virtual bool TryConvert(double value, out T result)
            {
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(long value, out T result)
            {
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(object value, out T result)
            {
                if (value == null)
                {
                    if (IsNullable)
                    {
                        result = default(T);
                        return true;
                    }
                }
                else
                {
                    switch (Type.GetTypeCode(value.GetType()))
                    {
                        case TypeCode.Boolean:
                            return TryConvert((bool)value, out result);
                        case TypeCode.Char:
                            return TryConvert((char)value, out result);
                        case TypeCode.SByte:
                            return TryConvert((sbyte)value, out result);
                        case TypeCode.Byte:
                            return TryConvert((byte)value, out result);
                        case TypeCode.Int16:
                            return TryConvert((short)value, out result);
                        case TypeCode.UInt16:
                            return TryConvert((ushort)value, out result);
                        case TypeCode.Int32:
                            return TryConvert((int)value, out result);
                        case TypeCode.UInt32:
                            return TryConvert((uint)value, out result);
                        case TypeCode.Int64:
                            return TryConvert((long)value, out result);
                        case TypeCode.UInt64:
                            return TryConvert((ulong)value, out result);
                        case TypeCode.Single:
                            return TryConvert((float)value, out result);
                        case TypeCode.Double:
                            return TryConvert((double)value, out result);
                        case TypeCode.Decimal:
                            return TryConvert((decimal)value, out result);
                        case TypeCode.DateTime:
                            return TryConvert((DateTime)value, out result);
                        case TypeCode.String:
                            return TryConvert((string)value, out result);
                    }
                }

                // could not convert
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(float value, out T result)
            {
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(string value, out T result)
            {
                result = default(T);
                return false;
            }

            [CLSCompliant(false)]
            public virtual bool TryConvert(ulong value, out T result)
            {
                result = default(T);
                return false;
            }

            public virtual bool TryConvert(bool value, out T result)
            {
                return TryConvert(value ? 1ul : 0ul, out result);
            }

            public virtual bool TryConvert(byte value, out T result)
            {
                return TryConvert((ulong)value, out result);
            }

            public virtual bool TryConvert(char value, out T result)
            {
                return TryConvert((ulong)value, out result);
            }

            public virtual bool TryConvert(short value, out T result)
            {
                return TryConvert((long)value, out result);
            }

            public virtual bool TryConvert(int value, out T result)
            {
                return TryConvert((long)value, out result);
            }

            [CLSCompliant(false)]
            public virtual bool TryConvert(sbyte value, out T result)
            {
                return TryConvert((long)value, out result);
            }

            [CLSCompliant(false)]
            public virtual bool TryConvert(ushort value, out T result)
            {
                return TryConvert((ulong)value, out result);
            }

            [CLSCompliant(false)]
            public virtual bool TryConvert(uint value, out T result)
            {
                return TryConvert((ulong)value, out result);
            }
        }

        #endregion

        #region Nested type: DateTimeConverter

        /// <summary>
        /// The date time converter.
        /// </summary>
        public class DateTimeConverter : Converter<DateTime>
        {
            public override bool TryConvert(DateTime value, out DateTime result)
            {
                result = value;
                return true;
            }

            public override bool TryConvert(string value, out DateTime result)
            {
                return DateTime.TryParse(value, out result);
            }
        }

        #endregion

        #region Nested type: DecimalConverter

        /// <summary>
        /// The decimal converter.
        /// </summary>
        public class DecimalConverter : Converter<decimal>
        {
            public override bool TryConvert(string value, out decimal result)
            {
                return decimal.TryParse(value, out result);
            }

            public override bool TryConvert(decimal value, out decimal result)
            {
                result = value;
                return true;
            }

            public override bool TryConvert(double value, out decimal result)
            {
                result = (decimal)value;
                return true;
            }

            public override bool TryConvert(float value, out decimal result)
            {
                result = (decimal)value;
                return true;
            }

            public override bool TryConvert(long value, out decimal result)
            {
                result = value;
                return true;
            }
        }

        #endregion

        #region Nested type: DoubleConverter

        /// <summary>
        /// The double converter.
        /// </summary>
        public class DoubleConverter : Converter<double>
        {
            public override bool TryConvert(string value, out double result)
            {
                return double.TryParse(value, out result);
            }

            public override bool TryConvert(decimal value, out double result)
            {
                result = (double)value;
                return true;
            }

            public override bool TryConvert(double value, out double result)
            {
                result = value;
                return true;
            }

            public override bool TryConvert(float value, out double result)
            {
                result = value;
                return true;
            }
        }

        #endregion

        #region Nested type: IConverter

        public interface IConverter
        {
            Type TargetType { get; }
            bool TryConvert(object value, out object result);
        }

        #endregion

        #region Nested type: Int16Converter

        public class Int16Converter : OrdinalConverter<short>
        {
            public override bool TryConvert(string value, out short result)
            {
                return Int16.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out short result)
            {
                if (value < Int16.MinValue || value > Int16.MaxValue)
                {
                    result = default(short);
                    return false;
                }

                result = (short)value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out short result)
            {
                if (value > (ulong)short.MaxValue)
                {
                    result = default(short);
                    return false;
                }

                result = (short)value;
                return true;
            }
        }

        #endregion

        #region Nested type: Int32Converter

        public class Int32Converter : OrdinalConverter<int>
        {
            public override bool TryConvert(string value, out int result)
            {
                return Int32.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out int result)
            {
                if (value < Int32.MinValue || value > Int32.MaxValue)
                {
                    result = default(int);
                    return false;
                }

                result = (int)value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out int result)
            {
                if (value > Int32.MaxValue)
                {
                    result = default(int);
                    return false;
                }

                result = (int)value;
                return true;
            }
        }

        #endregion

        #region Nested type: Int64Converter

        public class Int64Converter : OrdinalConverter<long>
        {
            public override bool TryConvert(string value, out long result)
            {
                return Int64.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out long result)
            {
                if (value < Int64.MinValue || value > Int64.MaxValue)
                {
                    result = default(long);
                    return false;
                }

                result = value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out long result)
            {
                if (value > Int64.MaxValue)
                {
                    result = default(long);
                    return false;
                }

                result = (long)value;
                return true;
            }
        }

        #endregion

        #region Nested type: OrdinalConverter

        public abstract class OrdinalConverter<T> : Converter<T>
        {
            public override sealed bool TryConvert(decimal value, out T result)
            {
                var roundedValue = decimal.Round(value, 0);
                if (roundedValue > Int64.MaxValue || roundedValue < Int64.MinValue)
                {
                    result = default(T);
                    return false;
                }

                return roundedValue > 0.0M ? TryConvert((ulong)value, out result) : TryConvert((long)value, out result);
            }

            public override sealed bool TryConvert(double value, out T result)
            {
                if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
                {
                    result = default(T);
                    return false;
                }

                var roundedValue = Math.Round(value, 0);
                if (roundedValue > Int64.MaxValue - 1023 || roundedValue < Int64.MinValue)
                {
                    result = default(T);
                    return false;
                }

                return roundedValue > 0.0 ? TryConvert((ulong)value, out result) : TryConvert((long)value, out result);
            }
            
            public override sealed bool TryConvert(float value, out T result)
            {
                return TryConvert((double)value, out result);
            }
        }

        #endregion

        #region Nested type: SByteConverter

        [CLSCompliant(false)]
        public class SByteConverter : OrdinalConverter<sbyte>
        {
            public override bool TryConvert(string value, out sbyte result)
            {
                return SByte.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out sbyte result)
            {
                if (value < SByte.MinValue || value > SByte.MaxValue)
                {
                    result = default(sbyte);
                    return false;
                }

                result = (sbyte)value;
                return true;
            }

            public override bool TryConvert(ulong value, out sbyte result)
            {
                if (value > (ulong)SByte.MaxValue)
                {
                    result = default(sbyte);
                    return false;
                }

                result = (sbyte)value;
                return true;
            }
        }

        #endregion

        #region Nested type: SingleConverter

        public class SingleConverter : Converter<float>
        {
            public override bool TryConvert(string value, out float result)
            {
                return float.TryParse(value, out result);
            }

            public override bool TryConvert(decimal value, out float result)
            {
                result = (float)value;
                return true;
            }

            public override bool TryConvert(double value, out float result)
            {
                result = (float)value;
                return true;
            }

            public override bool TryConvert(float value, out float result)
            {
                result = value;
                return true;
            }
        }

        #endregion

        #region Nested type: StringConverter

        public class StringConverter : Converter<string>
        {
            public override bool TryConvert(bool value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(byte value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(char value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(DateTime value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(decimal value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(double value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(float value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(int value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(object value, out string result)
            {
                result = value != null ? value.ToString() : null;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(sbyte value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(long value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(short value, out string result)
            {
                result = value.ToString();
                return true;
            }

            public override bool TryConvert(string value, out string result)
            {
                result = value;
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(uint value, out string result)
            {
                result = value.ToString();
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ulong value, out string result)
            {
                result = value.ToString();
                return true;
            }

            [CLSCompliant(false)]
            public override bool TryConvert(ushort value, out string result)
            {
                result = value.ToString();
                return true;
            }
        }

        #endregion

        #region Nested type: UInt16Converter

        [CLSCompliant(false)]
        public class UInt16Converter : OrdinalConverter<ushort>
        {
            public override bool TryConvert(string value, out ushort result)
            {
                return UInt16.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out ushort result)
            {
                if (value < UInt16.MinValue || value > UInt16.MaxValue)
                {
                    result = default(ushort);
                    return false;
                }

                result = (ushort)value;
                return true;
            }

            public override bool TryConvert(ulong value, out ushort result)
            {
                if (value < UInt16.MinValue || value > UInt16.MaxValue)
                {
                    result = default(ushort);
                    return false;
                }

                result = (ushort)value;
                return true;
            }
        }

        #endregion

        #region Nested type: UInt32Converter

        [CLSCompliant(false)]
        public class UInt32Converter : OrdinalConverter<uint>
        {
            public override bool TryConvert(string value, out uint result)
            {
                return UInt32.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out uint result)
            {
                if (value < UInt32.MinValue || value > UInt32.MaxValue)
                {
                    result = default(uint);
                    return false;
                }

                result = (uint)value;
                return true;
            }

            public override bool TryConvert(ulong value, out uint result)
            {
                if (value < UInt32.MinValue || value > UInt32.MaxValue)
                {
                    result = default(uint);
                    return false;
                }

                result = (uint)value;
                return true;
            }
        }

        #endregion

        #region Nested type: UInt64Converter

        [CLSCompliant(false)]
        public class UInt64Converter : OrdinalConverter<ulong>
        {
            public override bool TryConvert(string value, out ulong result)
            {
                return UInt64.TryParse(value, out result);
            }

            public override bool TryConvert(long value, out ulong result)
            {
                if (value < 0L)
                {
                    result = default(ulong);
                    return false;
                }

                result = (ulong)value;
                return true;
            }

            public override bool TryConvert(ulong value, out ulong result)
            {
                result = value;
                return false;
            }
        }

        #endregion
    }
}